﻿using System;
using YKXiangQi.Common;
using YKXiangQi.Enums;
using YKXiangQi.Models;

namespace YKXiangQi.Rules
{
    public class XiangQiRule: Rule
    {
        private Point[] KnightCheck = new Point[] 
            {
                new Point(-1,2),new Point(-1,-2),
                new Point(1,2),new Point(1,-2),
                new Point(-2,-1),new Point(-2,1),
                new Point(2,-1),new Point(2,1)
            };
        private Point[] KnightObstacle = new Point[]
            {
                new Point(-1,1),new Point(-1,-1),
                new Point(1,1), new Point(1,-1),
                new Point(-1,-1),new Point(-1,1),
                new Point(1,-1), new Point(1,1)
            };

        private int[,] LegalPosition = new int[,]
        {
            {1, 1, 5, 19, 3, 19, 5, 1, 1},
            {1, 1, 1, 3, 19, 3, 1, 1, 1},
            {5, 1, 1, 19, 7, 19, 1, 1, 5},
            {9, 1, 9, 1, 9, 1, 9, 1, 9},
            {9, 1, 13, 1, 9, 1, 13, 1, 9},
            {9, 9, 9, 9, 9, 9, 9, 9, 9},
            {9, 9, 9, 9, 9, 9, 9, 9, 9},
            {9, 9, 9, 9, 9, 9, 9, 9, 9},
            {9, 9, 9, 9, 9, 9, 9, 9, 9},
            {9, 9, 9, 9, 9, 9, 9, 9, 9}
        };

        public override bool IsValidMove(Move move)
        {
            var isValidMove = false;

            var piece = GetPieceAtPosition(Pieces, move.From);
            int ny = piece.Side == Sides.Black ? move.To.Y : 9 - move.To.Y;
            if ((LegalPosition[ny, move.To.X] & piece.PieceMaskValue) == 0)
                return false; //piece is not allowed at [to] position

            var pieceAtToPosition = GetPieceAtPosition(Pieces, move.To);
            if (pieceAtToPosition != null && pieceAtToPosition.Side == piece.Side)
                return false;


            switch (piece.Name)
            {
                case Constant.King:
                    {
                        if (Distance(move.From.X, move.To.X) + Distance(move.From.Y, move.To.Y) == 1)
                            isValidMove = true;
                        break;
                    }
                case Constant.Advisor:
                    {
                        if (Distance(move.From.X, move.To.X) == 1 && Distance(move.From.Y, move.To.Y) == 1)
                            isValidMove = true;
                        break;
                    }
                case Constant.Elephant:
                    {
                        var obstacleX = (move.From.X < move.To.X) ? move.From.X + 1 : move.From.X - 1;
                        var obstacleY = (move.From.Y < move.To.Y) ? move.From.Y + 1 : move.From.Y - 1;
                        var obstacle = GetPieceAtPosition(Pieces, new Point(obstacleX, obstacleY));

                        if (Distance(move.From.X, move.To.X) == 2 && Distance(move.From.Y, move.To.Y) == 2 && obstacle == null)
                            isValidMove = true;
                        break;
                    }
                case Constant.Cannon:
                    {
                        if (move.From.Y == move.To.Y) //horizontal move
                        {
                            int dx = move.From.X > move.To.X ? -1 : 1;
                            int numOfObstacles = 0;
                            for (int x = move.From.X + dx; x != move.To.X; x += dx)
                            {
                                Piece obstacle = GetPieceAtPosition(Pieces, new Point(x, move.From.Y));
                                if (obstacle != null)
                                    numOfObstacles++;
                            }

                            if ((numOfObstacles == 0 && pieceAtToPosition == null) || (numOfObstacles == 1 && pieceAtToPosition != null))
                                isValidMove = true;

                        }
                        else if (move.From.X == move.To.X) //vertical move
                        {
                            int dy = move.From.Y > move.To.Y ? -1 : 1;
                            int numOfObstacles = 0;
                            for (int y = move.From.Y + dy; y != move.To.Y; y += dy)
                            {
                                Piece obstacle = GetPieceAtPosition(Pieces, new Point(move.From.X, y));
                                if (obstacle != null)
                                    numOfObstacles++;
                            }

                            if ((numOfObstacles == 0 && pieceAtToPosition == null) || (numOfObstacles == 1 && pieceAtToPosition != null))
                                isValidMove = true;
                        }
                        break;
                    }
                case Constant.Rook:
                    {
                        if (move.From.Y == move.To.Y) //horizontal move
                        {
                            int dx = move.From.X > move.To.X ? -1 : 1;
                            int numOfObstacles = 0;
                            for (int x = move.From.X + dx; x != move.To.X; x += dx)
                            {
                                Piece obstacle = GetPieceAtPosition(Pieces, new Point(x, move.From.Y));
                                if (obstacle != null)
                                {
                                    numOfObstacles++;
                                    break;
                                }
                            }

                            if (numOfObstacles == 0)
                                isValidMove = true;
                        }
                        else if (move.From.X == move.To.X) //vertical move
                        {
                            int dy = move.From.Y > move.To.Y ? -1 : 1;
                            int numOfObstalces = 0;
                            for (int y = move.From.Y + dy; y != move.To.Y; y += dy)
                            {
                                Piece obstacle = GetPieceAtPosition(Pieces, new Point(move.From.X, y));
                                if (obstacle != null)
                                {
                                    numOfObstalces++;
                                    break;
                                }
                            }

                            if (numOfObstalces == 0)
                                isValidMove = true;
                        }

                        break;
                    }
                case Constant.Horse:
                    {
                        if (Distance(move.From.X, move.To.X) == 1 && Distance(move.From.Y, move.To.Y) == 2)
                        {
                            int dy = move.From.Y < move.To.Y ? 1 : -1;
                            var obstacle = GetPieceAtPosition(Pieces, new Point(move.From.X, move.From.Y + dy));
                            if (obstacle == null)
                                isValidMove = true;
                        }
                        else if (Distance(move.From.X, move.To.X) == 2 && Distance(move.From.Y, move.To.Y) == 1)
                        {
                            int dx = move.From.X < move.To.X ? 1 : -1;
                            var obstacle = GetPieceAtPosition(Pieces, new Point(move.From.X + dx, move.From.Y));
                            if (obstacle == null)
                                isValidMove = true;
                        }

                        break;
                    }
                case Constant.Pawn:
                    {
                        int dy = piece.Side == Sides.Black ? 1 : -1;
                        if ((move.From.X == move.To.X) && (move.From.Y + dy == move.To.Y))
                            isValidMove = true;
                        if ((move.From.Y == move.To.Y) && (Distance(move.From.X, move.To.X) == 1))
                            isValidMove = true;
                        break;
                    }
            }

            if (isValidMove)
            {
                var pieces = new Piece[10, 9];
                Array.Copy(Pieces, pieces, Pieces.Length);
                pieces[move.To.Y, move.To.X] = pieces[move.From.Y, move.From.X];
                pieces[move.From.Y, move.From.X] = null;

                if (IsKingChecked(piece.Side, pieces))
                    isValidMove = false;
            }

            return isValidMove;
        }

        private bool IsKingChecked(Sides kingSide, Piece[,] piecesOnBoard)
        {
            Point kingPosition = FindKingPosition(kingSide, piecesOnBoard);

            #region check horizontal line
            for (int i = -1; i < 2; i = i + 2) //-1;1
            {
                int numOfObstacles = 0;
                for (int x = kingPosition.X + i; x >= 0 && x <= 8; x = x + i)
                {
                    if (numOfObstacles > 1) break;

                    Piece currentPiece = piecesOnBoard[kingPosition.Y, x];
                    if (currentPiece != null)
                    {
                        if (currentPiece.Side != kingSide)//oops! we meet the enemy :(
                        {
                            switch (currentPiece.Name)
                            {
                                case Constant.Pawn:
                                    {
                                        if (Math.Abs(x - kingPosition.X) == 1)
                                            return true;
                                        break;
                                    }
                                case Constant.Rook:
                                    {
                                        if (numOfObstacles == 0)
                                            return true;
                                        break;
                                    }
                                case Constant.Cannon:
                                    {
                                        if (numOfObstacles == 1)
                                            return true;
                                        break;
                                    }
                            }
                            numOfObstacles++;
                        }
                        else
                            numOfObstacles++;
                    }
                }
            }
            #endregion

            #region check vertical line
            for (int i = -1; i < 2; i = i + 2) //-1;1
            {
                int numOfObstacles = 0;
                for (int y = kingPosition.Y + i; y >= 0 && y <= 9; y = y + i)
                {
                    if (numOfObstacles > 1) break;

                    Piece currentPiece = piecesOnBoard[y, kingPosition.X];
                    if (currentPiece != null)
                    {
                        if (currentPiece.Side != kingSide)//oops! we meet the enemy :(
                        {
                            switch (currentPiece.Name)
                            {
                                case Constant.Pawn:
                                    {
                                        if (y == kingPosition.Y + 1 - ((int)kingSide) * 2)
                                            return true;
                                        break;
                                    }
                                case Constant.Rook:
                                case Constant.King:
                                    {
                                        if (numOfObstacles == 0)
                                            return true;
                                        break;
                                    }
                                case Constant.Cannon:
                                    {
                                        if (numOfObstacles == 1)
                                            return true;
                                        break;
                                    }
                            }
                            numOfObstacles++;
                        }
                        else
                            numOfObstacles++;
                    }
                }
            }
            #endregion

            #region knight check
            for (int i = 0; i < 8; i++)
            {
                Piece piece = GetPieceAtPosition(piecesOnBoard, new Point(kingPosition.X + KnightCheck[i].X, kingPosition.Y + KnightCheck[i].Y));
                if (piece != null && piece.Side != kingSide && piece is Horse)
                {
                    Piece obstacle = GetPieceAtPosition(piecesOnBoard, new Point(kingPosition.X + KnightObstacle[i].X, kingPosition.Y + KnightObstacle[i].Y));
                    if (obstacle == null)
                        return true;
                }
            }
            #endregion

            return false;
        }

        private Point FindKingPosition(Sides kingSide, Piece[,] piecesOnBoard)
        {
            int offset = 0;
            if (kingSide == Sides.Red)
                offset += 7;

            for (int x = 3; x < 6; x++)
            {
                for (int y = 0; y < 3; y++)
                {
                    if (piecesOnBoard[y + offset, x] is King)
                        return new Point(x, y + offset);
                }
            }

            return null;
        }

        private Piece GetPieceAtPosition(Piece[,] piecesOnBoard, Point point)
        {
            if (point.X < 0 || point.X > 8 || point.Y < 0 || point.Y > 9)
                return null;

            return piecesOnBoard[point.Y, point.X];
        }

        private int Distance(int d1, int d2)
        {
            return Math.Abs(d1 - d2);
        }
    }
}
